import bisect
from lib import (
    BLACK_ICON_TEXT_STYLE,
    draw_lanes,
    draw_template,
    drawing_save,
    EXTRA_HEIGHT,
    GREY,
    ICON_TEXT_STYLE,
    LANE_THICKNESS,
    sect_ths,
    TEXTURE_COLOR_0,
    TEXTURE_COLOR_1,
    TEXTURE_COLOR_2,
    TEXTURE_COLOR_3,
    TEXTURE_COLOR_4,
    TIME_WIDTH,
    TIME_X,
    TOTAL_DUR,
    WIDTH,
)
import svgwrite

FILENAME = "textures.svg"

LANE_HEIGHT = 100

VOICE_COLOR_0, VOICE_COLOR_1, VOICE_COLOR_2, VOICE_COLOR_3 = (
    "#a6e6e9",
    "#f3d7ca",
    "#f0c1e1",
    "#cce0ac",
)

VOICE_HEIGHT = LANE_HEIGHT / 6
VOICE_THICKNESS = 3 * VOICE_HEIGHT / 4

VOX, GTR_1, GTR_2, BASS, DRUMKIT = 0, 1, 2, 3, 4
TEXTURE_EVENTS = [
    # start, end, instr, voices, relation
    (0, 15, GTR_1, 3, None),
    (15, 1 * 60 + 10, GTR_1, 1, None),
    (15, 55, GTR_2, 1, "H"),
    (15, 1 * 60 + 10, BASS, 1, "H"),
    (15, 41, DRUMKIT, 3, "S"),
    (41, 55, DRUMKIT, 4, "S"),
    (55, 1 * 60 + 10, GTR_2, 2, "Hp"),
    (55, 1 * 60 + 10, DRUMKIT, 4, "R"),
    (1 * 60 + 10, 1 * 60 + 15, GTR_1, 2, None),
    (1 * 60 + 10, 1 * 60 + 15, GTR_2, 2, "Rp"),
    (1 * 60 + 10, 1 * 60 + 15, BASS, 1, "R"),
    (1 * 60 + 10, 1 * 60 + 15, DRUMKIT, 3, "S"),
    (1 * 60 + 15, 1 * 60 + 32, GTR_1, 1, None),
    (1 * 60 + 15, 1 * 60 + 32, GTR_2, 2, "Hp"),
    (1 * 60 + 15, 1 * 60 + 32, BASS, 1, "H"),
    (1 * 60 + 15, 1 * 60 + 24, DRUMKIT, 3, "R"),
    (1 * 60 + 24, 1 * 60 + 32, DRUMKIT, 3, "S"),
    (1 * 60 + 32, 1 * 60 + 45, GTR_1, 2, None),
    (1 * 60 + 32, 1 * 60 + 45, GTR_2, 2, "Rp"),
    (1 * 60 + 32, 1 * 60 + 39, BASS, 1, "M"),
    (1 * 60 + 32, 1 * 60 + 39, DRUMKIT, 3, "R"),
    (1 * 60 + 45, 1 * 60 + 46, GTR_1, 1, None),
    (1 * 60 + 45, 1 * 60 + 46, GTR_2, 1, "H"),
    (1 * 60 + 45, 1 * 60 + 49, DRUMKIT, 3, "R"),
    (1 * 60 + 46, 1 * 60 + 48, GTR_1, 2, None),
    (1 * 60 + 46, 1 * 60 + 48, GTR_2, 2, "R"),
    (1 * 60 + 46, 1 * 60 + 48, BASS, 1, "M"),
    (1 * 60 + 48, 1 * 60 + 49, GTR_1, 1, None),
    (1 * 60 + 48, 1 * 60 + 49, GTR_2, 1, "H"),
    (1 * 60 + 49, 1 * 60 + 51, GTR_1, 3, None),
    (1 * 60 + 49, 1 * 60 + 51, GTR_2, 1, "R"),
    (1 * 60 + 49, 1 * 60 + 51, BASS, 1, "R"),
    (1 * 60 + 49, 1 * 60 + 51, DRUMKIT, 3, "S"),
    (1 * 60 + 51, 1 * 60 + 52, GTR_1, 1, None),
    (1 * 60 + 51, 1 * 60 + 52, GTR_2, 1, "H"),
    (1 * 60 + 51, 1 * 60 + 55, DRUMKIT, 3, "R"),
    (1 * 60 + 52, 1 * 60 + 54, GTR_1, 2, None),
    (1 * 60 + 52, 1 * 60 + 54, GTR_2, 2, "R"),
    (1 * 60 + 52, 1 * 60 + 54, BASS, 1, "M"),
    (1 * 60 + 54, 1 * 60 + 55, GTR_1, 1, None),
    (1 * 60 + 54, 1 * 60 + 55, GTR_2, 1, "H"),
    (1 * 60 + 55, 1 * 60 + 57, GTR_1, 3, None),
    (1 * 60 + 55, 1 * 60 + 57, GTR_2, 1, "R"),
    (1 * 60 + 55, 1 * 60 + 57, BASS, 1, "R"),
    (1 * 60 + 55, 1 * 60 + 57, DRUMKIT, 3, "S"),
    (1 * 60 + 57, 1 * 60 + 59, GTR_1, 2, None),
    (1 * 60 + 57, 2 * 60 + 5, GTR_2, 1, "A"),
    (1 * 60 + 57, 2 * 60 + 5, BASS, 1, "H"),
    (1 * 60 + 57, 2 * 60 + 5, DRUMKIT, 2, "R"),
    (1 * 60 + 59, 2 * 60 + 1, GTR_1, 3, None),
    (2 * 60 + 1, 2 * 60 + 3, GTR_1, 2, None),
    (2 * 60 + 3, 2 * 60 + 5, GTR_1, 3, None),
    (2 * 60 + 5, 2 * 60 + 27, VOX, 1, None),
    (2 * 60 + 5, 2 * 60 + 27, GTR_1, 3, "A"),
    (2 * 60 + 5, 2 * 60 + 27, GTR_2, 2, "A"),
    (2 * 60 + 5, 2 * 60 + 27, BASS, 1, "A"),
    (2 * 60 + 5, 2 * 60 + 24, DRUMKIT, 4, "S"),
    (2 * 60 + 24, 2 * 60 + 27, DRUMKIT, 3, "A"),
    (2 * 60 + 27, 2 * 60 + 47, GTR_1, 1, None),
    (2 * 60 + 27, 2 * 60 + 47, GTR_2, 1, "H"),
    (2 * 60 + 36, 2 * 60 + 44, BASS, 1, "H"),
    (2 * 60 + 36, 2 * 60 + 44, DRUMKIT, 3, "R"),
    (2 * 60 + 47, 2 * 60 + 52, GTR_1, 1, None),
    (2 * 60 + 47, 2 * 60 + 49.5, GTR_2, 2, "A"),
    (2 * 60 + 47, 3 * 60 + 10, BASS, 1, "A"),
    (2 * 60 + 47, 3 * 60 + 10, DRUMKIT, 4, "R"),
    (2 * 60 + 49.5, 2 * 60 + 52, GTR_2, 3, "A"),
    (2 * 60 + 52, 2 * 60 + 55, VOX, 1, None),
    (2 * 60 + 52, 2 * 60 + 55, GTR_1, 4, "A"),
    (2 * 60 + 52, 2 * 60 + 57.5, GTR_2, 2, "A"),
    (2 * 60 + 55, 3 * 60 + 0, GTR_1, 1, None),
    (2 * 60 + 57.5, 3 * 60 + 0, GTR_2, 3, "A"),
    (3 * 60 + 0, 3 * 60 + 3, VOX, 1, None),
    (3 * 60 + 0, 3 * 60 + 3, GTR_1, 4, "A"),
    (3 * 60 + 0, 3 * 60 + 3, GTR_2, 2, "A"),
    (3 * 60 + 3, 3 * 60 + 10, GTR_1, 1, None),
    (3 * 60 + 3, 3 * 60 + 10, GTR_2, 1, "H"),
    (3 * 60 + 10, 3 * 60 + 31, GTR_1, 2, None),
    (3 * 60 + 10, 3 * 60 + 31, GTR_2, 2, "H"),
    (3 * 60 + 15, 3 * 60 + 31, BASS, 1, "C"),
    (3 * 60 + 15, 3 * 60 + 31, DRUMKIT, 5, "C"),
    (3 * 60 + 31, 3 * 60 + 39, GTR_1, 2, None),
    (3 * 60 + 31, 3 * 60 + 39, GTR_2, 2, "R"),
    (3 * 60 + 31, 3 * 60 + 48, BASS, 1, "R"),
    (3 * 60 + 31, 3 * 60 + 39, DRUMKIT, 2, "R"),
    (3 * 60 + 39, 3 * 60 + 48, GTR_1, 3, None),
    (3 * 60 + 39, 3 * 60 + 48, GTR_2, 3, "R"),
    (3 * 60 + 39, 4 * 60 + 15, DRUMKIT, 4, "S"),
    (3 * 60 + 48, 4 * 60 + 15, VOX, 1, None),
    (3 * 60 + 48, 4 * 60 + 15, GTR_1, 3, "A"),
    (3 * 60 + 48, 4 * 60 + 15, GTR_2, 3, "A"),
    (3 * 60 + 48, 4 * 60 + 15, BASS, 1, "A"),
    (4 * 60 + 15, 4 * 60 + 21, GTR_1, 3, None),
    (4 * 60 + 15, 4 * 60 + 21, GTR_2, 3, "R"),
    (4 * 60 + 15, 4 * 60 + 21, BASS, 1, "R"),
    (4 * 60 + 15, 4 * 60 + 21, DRUMKIT, 3, "R"),
    (4 * 60 + 21, 4 * 60 + 44, GTR_1, 1, None),
    (4 * 60 + 21, 4 * 60 + 23.5, GTR_2, 1, "H"),
    (4 * 60 + 21, 4 * 60 + 23.5, BASS, 1, "M"),
    (4 * 60 + 21, 4 * 60 + 23.5, DRUMKIT, 5, "R"),
    (4 * 60 + 23.5, 4 * 60 + 26.25, GTR_2, 3, "A"),
    (4 * 60 + 23.5, 4 * 60 + 26.25, BASS, 1, "A"),
    (4 * 60 + 23.5, 4 * 60 + 31, DRUMKIT, 3, "S"),
    (4 * 60 + 26.25, 4 * 60 + 28.25, GTR_2, 1, "H"),
    (4 * 60 + 26.25, 4 * 60 + 28.25, BASS, 1, "M"),
    (4 * 60 + 28.25, 4 * 60 + 31, GTR_2, 3, "A"),
    (4 * 60 + 28.25, 4 * 60 + 31, BASS, 1, "A"),
    (4 * 60 + 31, 4 * 60 + 33.5, GTR_2, 1, "H"),
    (4 * 60 + 31, 4 * 60 + 33.5, BASS, 1, "M"),
    (4 * 60 + 31, 4 * 60 + 33.5, DRUMKIT, 5, "R"),
    (4 * 60 + 33.5, 4 * 60 + 36, GTR_2, 3, "A"),
    (4 * 60 + 33.5, 4 * 60 + 36, BASS, 1, "A"),
    (4 * 60 + 33.5, 4 * 60 + 41, DRUMKIT, 3, "S"),
    (4 * 60 + 36, 4 * 60 + 38.25, GTR_2, 1, "H"),
    (4 * 60 + 36, 4 * 60 + 38.25, BASS, 1, "M"),
    (4 * 60 + 38.25, 4 * 60 + 41, GTR_2, 1, "A"),
    (4 * 60 + 38.25, 4 * 60 + 41, BASS, 1, "A"),
    (4 * 60 + 41, 4 * 60 + 44, GTR_2, 1, "H"),
    (4 * 60 + 41, 4 * 60 + 44, BASS, 1, "M"),
    (4 * 60 + 41, 4 * 60 + 44, DRUMKIT, 5, "R"),
]
LANES = [
    ("vocals", 6),
    ("guitar 1", 3),
    ("guitar 2", 1),
    ("bass", 13),
    ("drumkit", 0),
]

HEIGHT = len(LANES) * LANE_HEIGHT + EXTRA_HEIGHT


def relation_color(s):
    match s[0]:
        case "H":
            return TEXTURE_COLOR_0
        case "C":
            return TEXTURE_COLOR_1
        case "R":
            return TEXTURE_COLOR_2
        case "A":
            return TEXTURE_COLOR_3
        case "S":
            return TEXTURE_COLOR_4
        case "M":
            return GREY
        case _:
            raise ValueError(f"Unrecognized relation: {s}")


def relation_name(s):
    main_str = None
    match s[0]:
        case "H":
            main_str = "home&shy;ophony"
        case "C":
            main_str = "counter&shy;point"
        case "R":
            main_str = "homo&shy;rhythm"
        case "A":
            main_str = "accompaniment"
        case "S":
            main_str = "syncopation"
        case "M":
            main_str = "mono&shy;phony"
        case _:
            raise ValueError(f"Unrecognized relation: {s}")

    if len(s) > 1 and s[1] == "p":
        main_str += " (with pedal&shy;point)"

    return main_str


def main():
    dwg = svgwrite.Drawing(filename=FILENAME, size=(WIDTH, HEIGHT), debug=True)

    draw_template(dwg, len(LANES), LANE_HEIGHT)
    draw_lanes(dwg, LANES, LANE_HEIGHT)

    for start, end, instr, voices, relation in TEXTURE_EVENTS:
        x_start_raw, x_end_raw = TIME_X + TIME_WIDTH * (
            start / TOTAL_DUR
        ), TIME_X + TIME_WIDTH * (end / TOTAL_DUR)
        x_start, x_end = x_start_raw - 0.5, x_end_raw + 0.5

        lane_y = LANE_HEIGHT * instr - LANE_THICKNESS
        lane_center_y = lane_y + LANE_HEIGHT / 2 + LANE_THICKNESS / 2
        voices_start_y = lane_center_y - (voices - 1) * VOICE_HEIGHT / 2

        vcolor = None
        if instr == VOX:
            vcolor = VOICE_COLOR_0
        elif instr in (GTR_1, GTR_2):
            vcolor = VOICE_COLOR_3
        elif instr == BASS:
            vcolor = VOICE_COLOR_1
        elif instr == DRUMKIT:
            vcolor = VOICE_COLOR_2

        for y in [voices_start_y + n * VOICE_HEIGHT for n in range(voices)]:
            voice_line = dwg.add(dwg.line(start=(x_start, y), end=(x_end, y)))
            voice_line.stroke(vcolor, width=VOICE_THICKNESS)

        if relation is not None:
            relation_top = lane_y - LANE_HEIGHT / 4 + VOICE_THICKNESS / 2
            if instr in (GTR_1, DRUMKIT):
                relation_top -= VOICE_HEIGHT + VOICE_THICKNESS / 2
            elif instr == BASS:
                relation_top -= VOICE_THICKNESS / 2
            rect_width, rect_height = (
                x_end_raw - x_start_raw,
                voices_start_y - relation_top - VOICE_THICKNESS / 2,
            )
            relation_box = dwg.add(
                dwg.rect(
                    insert=(x_start_raw, relation_top),
                    size=(rect_width, rect_height),
                )
            )
            relation_box.fill(color=relation_color(relation), opacity=0.75)

            if rect_width > 24:
                text_x_offset = 7 if len(relation) < 2 else 13
                text_insert = (
                    x_start_raw + rect_width / 2 - text_x_offset,
                    relation_top + rect_height / 2 + 7,
                )
                style = (
                    BLACK_ICON_TEXT_STYLE
                    if relation[0] == "R"
                    else ICON_TEXT_STYLE
                )
                dwg.add(dwg.text(relation, insert=text_insert, style=style))

    drawing_save(dwg)

    ts = set()
    for s, e, _, _, _ in TEXTURE_EVENTS:
        ts.add(s)
        ts.add(e)
    ts = list(ts)
    ts.sort()

    table = """<table>
  <thead>
    <tr>
      <td colspan="2" rowspan="2"></td>"""
    table += sect_ths(ts)

    for instr_i, name in enumerate(
        ["vocals", "guitar&nbsp;1", "guitar&nbsp;2", "bass", "drumkit"]
    ):
        textures, voice_counts = "", ""
        deferred_empty_texture = 0
        prev_end_i = 0
        for start, end, instr_j, voices, relation in TEXTURE_EVENTS + [
            (4 * 60 + 44, float("inf"), n, "", None) for n in range(5)
        ]:
            if instr_i != instr_j:
                continue

            start_i, end_i = bisect.bisect_left(ts, start), bisect.bisect_left(
                ts, end
            )
            delta = end_i - start_i

            if start_i != prev_end_i:
                skipped = start_i - prev_end_i
                skipped_colspan_text = (
                    f' colspan="{skipped}"' if skipped != 1 else ""
                )
                if instr_i != 0:
                    textures += f'<td{skipped_colspan_text} rowspan="2"></td>'
                else:
                    voice_counts += f"<td{skipped_colspan_text}></td>"

            colspan_text = f' colspan="{delta}"' if delta != 1 else ""
            if relation is not None:
                if deferred_empty_texture > 0:
                    deferred_colspan_text = (
                        f' colspan="{deferred_empty_texture}"'
                        if deferred_empty_texture != 1
                        else ""
                    )
                    textures += f"<td{deferred_colspan_text}></td>"
                    deferred_empty_texture = 0
                textures += f'<td{colspan_text} class="center"><abbr title="{relation_name(relation)}" class="roman">{relation}</abbr></td>'
            else:
                deferred_empty_texture += delta
            voice_counts += f'<td{colspan_text} class="center">{voices}</td>'

            prev_end_i = end_i

        if instr_i != 0:
            table += f"""
    <tr>
      <th scope="row" rowspan="2" class="no-wrap">{name}</th>
      <th scope="row" class="no-wrap">texture</th>
      {textures}
    </tr>
    <tr>
      <th scope="row" class="no-wrap">voices</th>
      {voice_counts}
    </tr>"""
        else:
            table += f"""
    <tr>
      <th scope="row" class="no-wrap">{name}</th>
      <th scope="row" class="no-wrap">voices</th>
      {voice_counts}
    </tr>"""

    table += """
  </tbody>
</table>"""

    print(table)


if __name__ == "__main__":
    main()
